/**
 * Copyright 2017 orivil Authors. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package swagger

import (
	"encoding/json"
	"fmt"
	"os"
	"io"
	"net/http"
)

var API2 = &OpenApi2{
	Info: &Info{
		Title:          "Orivil Api Document",
		Description:    "support for OpenApi 2.0",
		Version:        "2.0",
		TermsOfService: "https://github.com/tugoers/orivil/terms",
		Contact: &Contact{
			Name:  "tugoer",
			Url:   "https://github.com/tugoer",
			Email: "tugoer@orivil.com",
		},
		License: &License{
			Name: "MIT",
			Url:  "https://mit-license.org",
		},
	},
	Swagger:             "2.0",
	Schemes:             []string{"http"},
	Paths:               make(Paths),
	Definitions:         make(Definitions),
	Responses:           make(Responses),
	Parameters:          make(map[string]*Parameter),
	SecurityDefinitions: make(SecurityDefinitions),
}

type OpenApi2 struct {
	// Required.
	// Specifies the Swagger Specification version being used.
	// It can be used by the Swagger UI and other clients to interpret the API listing.
	// The value MUST be "2.0".
	Swagger string `json:"swagger"`

	// Required.
	// Provides metadata about the API.
	// The metadata can be used by the clients if needed.
	Info *Info `json:"info"`

	// The host (name or ip) serving the API.
	// This MUST be the host only and does not include the scheme nor sub-paths.
	// It MAY include a port. If the host is not included, the host serving the
	// documentation is to be used (including the port). The host does not support
	// path template(http://swagger.io/specification/#pathTemplating).
	Host string `json:"host,omitempty"`

	// The base path on which the API is served, which is relative to the host.
	// If it is not included, the API is served directly under the host.
	// The value MUST start with a leading slash (/). The basePath does not support
	// path template(http://swagger.io/specification/#pathTemplating).
	BasePath string `json:"basePath,omitempty"`

	// The transfer protocol of the API.
	// Values MUST be from the list: "http", "https", "ws", "wss".
	// If the schemes is not included, the default scheme to be used is the one used
	// to access the Swagger definition itself.
	Schemes []string `json:"schemes,omitempty"`

	// A list of MIME types the APIs can consume.
	// This is global to all APIs but can be overridden on specific API calls.
	// Value MUST be as described under Mime Types(http://swagger.io/specification/#mimeTypes).
	Consumes []MimeType `json:"consumes,omitempty"`

	// A list of MIME types the APIs can produce.
	// This is global to all APIs but can be overridden on specific API calls.
	// Value MUST be as described under Mime Types(http://swagger.io/specification/#mimeTypes).
	Produces []MimeType `json:"produces,omitempty"`

	// Required.
	// The available paths and operations for the API.
	Paths Paths `json:"paths"`

	// An object to hold data types produced and consumed by operations.
	Definitions Definitions `json:"definitions,omitempty"`

	// An object to hold parameters that can be used across operations.
	// This property does not define global parameters for all operations.
	Parameters map[string]*Parameter `json:"parameters,omitempty"`

	// An object to hold responses that can be used across operations.
	// This property does not define global responses for all operations.
	Responses Responses `json:"responses,omitempty"`

	// Security scheme definitions that can be used across the specification.
	SecurityDefinitions SecurityDefinitions `json:"securityDefinitions,omitempty"`

	// A declaration of which security schemes are applied for the API as a whole.
	// The list of values describes alternative security schemes that can be used
	// (that is, there is a logical OR between the security requirements).
	// Individual operations can override this definition.
	Security []SecurityRequirement `json:"security,omitempty"`

	// A list of tags used by the specification with additional metadata. The order
	// of the tags can be used to reflect on their order by the parsing tools.
	// Not all tags that are used by the Operation Object(http://swagger.io/specification/#operationObject)
	// must be declared. The tags that are not declared may be organized randomly or
	// based on the tools' logic. Each tag name in the list MUST be unique.
	Tags []*Tag `json:"tags,omitempty"`

	// Additional external documentation.
	ExternalDocs *ExternalDoc `json:"externalDocs,omitempty"`
}

func (as *OpenApi2) AddSecurityDefinition(name string, definition *SecurityScheme) {
	if _, ok := as.SecurityDefinitions[name]; ok {
		panic(fmt.Errorf("security definition [%s] already exist", name))
	}
	as.SecurityDefinitions[name] = definition
}

func (as *OpenApi2) AddTags(t ...*Tag) {
	as.Tags = append(as.Tags, t...)
}

func (as *OpenApi2) WriteTo(w io.Writer) error {
	return json.NewEncoder(w).Encode(as)
}

func (as *OpenApi2) WriteToFile(file string) error {
	f, err := os.Create(file)
	if err != nil {
		return err
	}
	defer f.Close()
	return as.WriteTo(f)
}

func (as *OpenApi2) AddOperation(method string, path string, op *Operation) {
	var item *PathItem
	if i, ok := as.Paths[path]; ok {
		item = i
	} else {
		item = &PathItem{}
		as.Paths[path] = item
	}
	switch method {
	case http.MethodGet:
		if item.Get == nil {
			item.Get = op
			return
		}
	case http.MethodPost:
		if item.Post == nil {
			item.Post = op
			return
		}
	case http.MethodPut:
		if item.Put == nil {
			item.Put = op
			return
		}
	case http.MethodDelete:
		if item.Delete == nil {
			item.Delete = op
			return
		}
	case http.MethodOptions:
		if item.Options == nil {
			item.Options = op
			return
		}
	case http.MethodHead:
		if item.Head == nil {
			item.Head = op
			return
		}
	case http.MethodPatch:
		if item.Patch == nil {
			item.Patch = op
			return
		}
	case http.MethodTrace:
		if item.Trace == nil {
			item.Trace = op
			return
		}
	}
	panic(&pathError{method: method, path: path})
}

type pathError struct {
	method string
	path string
}

func (pe *pathError) Error() string {
	return fmt.Sprintf("path duplicat %s/%s", pe.method, pe.path)
}